package com.fastfly.service;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Query;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.fastfly.domain.City;
import com.fastfly.domain.Customer;
import com.fastfly.domain.Flight;
import com.fastfly.domain.Reservation;
import com.fastfly.domain.Seat;
import com.fastfly.domain.Ticket;

@Service("flightService")
public class JpaFlightService extends JpaBase implements FlightService {

	private static final long serialVersionUID = 1L;

	public JpaFlightService() {
		super();
	}

	@Transactional
	public Reservation bookFlight(Customer customer, int numberPassengers, Flight flightDeparting, Flight flightReturning, String paymentType) throws Exception {
		log.debug("Simulate processing time. Sleeping 2 secs .... ");
		Thread.sleep(2000);
		
		Reservation reservation = null;

		reservation = new Reservation(customer, paymentType);

		List<Seat> listDepartingSeats = findAvailableSeatInFlight(flightDeparting.getId());
		List<Seat> listReturningSeats = findAvailableSeatInFlight(flightReturning.getId());

		reservation.bookFlight(numberPassengers, reservation, listDepartingSeats, listReturningSeats);

		store(reservation);

		return reservation;
	}

	@Override
	public Flight findById(long id) {
		return (Flight) em.find(Flight.class, id);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<City> findAllCities() {
		List<City> list = em.createQuery("select obj from City obj order by obj.name").getResultList();
		return list;
	}

	@SuppressWarnings("unchecked")
	public List<Seat> findAvailableSeatInFlight(long flightId) {
		Query query = em.createQuery("select obj from Seat obj where obj.flight.id = :flightId and obj.status = :status order by obj.number");
		query.setParameter("flightId", flightId);
		query.setParameter("status", Seat.AVAILABLE_STATUS);
		List<Seat> list = query.getResultList();
		return list;
	}

	// TODO
	// --select f.*, t.* from FLIGHT f, FLIGHT t where f.DESTINATIONCITY_ID = 1
	// and f.DEPARTINGCITY_ID = 2 and f.DEPARTUREDATE BETWEEN '2009-04-20' and
	// '2009-04-21' and t.DESTINATIONCITY_ID = 2 and t.DEPARTINGCITY_ID = 1 and
	// t.DEPARTUREDATE BETWEEN '2009-04-25' and '2009-04-26' ;

	@SuppressWarnings("unchecked")
	public List<Flight> findFlightsByCityAndSeats(Flight criteria) {
		String sql = "select h from Flight h where h.departingCity = :departingCity and h.destinationCity = :destinationCity and h.departureDate between :departureDate and :endDate and "
				+ "(select count(s) from Seat s where s.status = :status and s.flight.id = h.id) >= :numberPassengers  " + "order by h.departureDate";
		Query query = setupFindFlightsByCity(criteria, sql);

		query.setParameter("numberPassengers", (long) criteria.getNumberPassengers());
		query.setParameter("status", Seat.AVAILABLE_STATUS);

		return query.getResultList();
	}

	@SuppressWarnings("unchecked")
	public List<Flight> findReturnFlightsByCityAndSeats(Flight criteria) {
		String sql = "select h from Flight h where h.departingCity = :departingCity and h.destinationCity = :destinationCity and h.departureDate between :departureDate and :endDate and "
				+ "(select count(s) from Seat s where s.status = :status and s.flight.id = h.id) >= :numberPassengers  " + "order by h.departureDate";

		Calendar cal = setupCalendarByDepartureDate(criteria, criteria.getReturnDate());
		Query query = em.createQuery(sql);
		query.setParameter("departingCity", criteria.getDestinationCity());
		query.setParameter("destinationCity", criteria.getDepartingCity());
		query.setParameter("departureDate", criteria.getReturnDate());
		query.setParameter("endDate", cal.getTime());

		query.setParameter("numberPassengers", (long) criteria.getNumberPassengers());
		query.setParameter("status", Seat.AVAILABLE_STATUS);

		return query.getResultList();
	}

	@SuppressWarnings("unchecked")
	public List<Flight> findFlightsByCity(Flight criteria) {
		String sql = "select h from Flight h where h.departingCity = :departingCity and h.destinationCity = :destinationCity and h.departureDate between :departureDate and :endDate order by h.departureDate";
		Query query = setupFindFlightsByCity(criteria, sql);

		return query.getResultList();
	}

	protected Query setupFindFlightsByCity(Flight criteria, String sql) {
		Calendar cal = setupCalendarByDepartureDate(criteria, criteria.getDepartureDate());
		Query query = em.createQuery(sql);
		query.setParameter("departingCity", criteria.getDepartingCity());
		query.setParameter("destinationCity", criteria.getDestinationCity());
		query.setParameter("departureDate", criteria.getDepartureDate());
		query.setParameter("endDate", cal.getTime());
		return query;
	}

	@SuppressWarnings("unchecked")
	public List<Flight> findFlightsByNumber(Flight criteria) {
		Calendar cal = setupCalendarByDepartureDate(criteria, criteria.getDepartureDate());
		Query query = em.createQuery("select h from Flight h where h.flightNumber = :flightNumber and h.departureDate between :departureDate and :endDate order by h.departureDate");
		query.setParameter("flightNumber", criteria.getFlightNumber());
		query.setParameter("departureDate", criteria.getDepartureDate());
		query.setParameter("endDate", cal.getTime());
		return query.getResultList();
	}

	protected Calendar setupCalendarByDepartureDate(Flight criteria, Date date) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		cal.set(Calendar.HOUR_OF_DAY, 23);
		cal.set(Calendar.MINUTE, 59);
		cal.set(Calendar.SECOND, 59);
		cal.set(Calendar.MILLISECOND, 999);
		return cal;
	}

	@Override
	@Transactional
	public void cancelReservation(long reservationId) {
		Reservation reservation = (Reservation) findById(reservationId, Reservation.class);
		reservation.cancel();
		delete(reservationId, Reservation.class);
	}

	@Override
	@Transactional
	public Reservation changeSeats(Reservation reservation) {
		Map<Long, Seat> departureMap = new HashMap<Long, Seat>();
		Map<Long, Seat> returnMap = new HashMap<Long, Seat>();
		for (Ticket ticket : reservation.getTicketCollection()) {
			Seat depSeat = (Seat) findById(ticket.getNewSeatDepartureId(), Seat.class);
			log.debug("depSeat: " + depSeat + " - ticket.getNewSeatDepartureId(): " + ticket.getNewSeatDepartureId());

			Seat retSeat = (Seat) findById(ticket.getNewSeatReturningId(), Seat.class);
			log.debug("retSeat: " + retSeat + " - ticket.getNewSeatReturningId(): " + ticket.getNewSeatReturningId());

			if (departureMap.containsKey(ticket.getNewSeatDepartureId()) || returnMap.containsKey(ticket.getNewSeatReturningId())) {
				log.error("ticket.getNewSeatDepartureId(): " + ticket.getNewSeatDepartureId() + " - ticket.getNewSeatReturningId(): " + ticket.getNewSeatReturningId());
				log.error("You need to select different seats - departureMap: " + departureMap + " - returnMap: " + returnMap);
				throw new FastFlyException("You need to select different seats!");
			}

			departureMap.put(ticket.getNewSeatDepartureId(), depSeat);
			returnMap.put(ticket.getNewSeatReturningId(), retSeat);
		}
		List<Seat> oldSeats = reservation.changeSeats(departureMap, returnMap);
		for (Seat seat : oldSeats) {
			merge(seat);
		}
		reservation = (Reservation) store(reservation);
		return reservation;
	}

}